package energy;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Date;
import java.util.List;
import java.util.LinkedList;
import java.util.ListIterator;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.ButtonGroup;
import javax.swing.Timer;
import javax.swing.BoxLayout;
import javax.swing.Box;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.AbstractTableModel;

import org.apache.log4j.Logger;

public class UserInterface extends Thread {

	private static Logger log = Logger.getLogger("energy.UserInterface");
	private static final int UPDATE_DELAY = 1000;
	private static final int DEFAULT_GRAPH_WINDOW_SIZE = 10000;
	
	private Graph graph;
	private JTable counterTable;
	private JLabel totalTimeLabel;
	
	private Driver<EnergyData> driver;
	private Timer updateTimer;
	
	private Calibrator calibrator;
	private DataProcessor flagsProcessor;
	private Counter counter;
	private EnergyStorage storage;
	
	private boolean running;
	private boolean updateGraph;
	private int graphWindowSize;
	List<EnergyData> bufferedData;
	
	public UserInterface(Driver<EnergyData> d) {
		driver = d;
		storage = new EnergyStorage();
		running = false;
		updateGraph = true;
		graphWindowSize = DEFAULT_GRAPH_WINDOW_SIZE;
		calibrator = new Calibrator(Condensator.C33u);
		flagsProcessor = FlagsProcessorFactory.getEightStateProcessor();
		counter = new Counter(8);
		
		updateTimer = new Timer(UPDATE_DELAY, new ActionListener() {
			public void actionPerformed(ActionEvent evt) {  
				List<EnergyData> data = driver.getdata();
				if(data.isEmpty()) return;
				
				int latestTime = data.get(data.size()-1).time;
				
				/*if(!bufferedData.isEmpty()) {
					calibrator.calibrate(data.get(0), bufferedData.get(bufferedData.size()-1));
				}
				EnergyData previous = null;
				for(EnergyData d : data) {
					if(previous==null) {
						previous = d;
					} else {
						calibrator.calibrate(d, previous);
						previous = d;
					}
				}*/
				
				calibrator.process(data);
				flagsProcessor.process(data);
				counter.process(data);
				storage.add(data);

				bufferedData.addAll(data);
				ListIterator<EnergyData> it = bufferedData.listIterator();
				while(it.hasNext()) {
					if(it.next().time<latestTime-graphWindowSize*32.768) {
						it.remove();
					}
				}
//				for(EnergyData d : data) log.debug(d);
//				log.debug(bufferedData.size());
//				log.debug(bufferedData);
				
				if(updateGraph) graph.setData(bufferedData);
				counterTable.updateUI();
				totalTimeLabel.setText(""+(int)counter.getTotalTime() + "ms.");
			}
		});
	}
	
	@Override
	public void run() {
        
		/***** INFO *****/
		JPanel info = getPanel(BoxLayout.X_AXIS);
        info.add(getNormalLabel("Running: \t"));
        final JLabel runningLabel = getNormalLabel("NO");
        info.add(runningLabel);
        info.add(getNormalLabel(", Time: \t"));
        totalTimeLabel = getNormalLabel("Not started.");
        info.add(totalTimeLabel);
        /*numberMessagesLabel = new new JLabel(", #Msg: \t")
        info.add(numberMessagesLabel);*/
        //info.add(new JLabel(""+currentData.size()));
        
        Container infoControls = getPanel(BoxLayout.Y_AXIS);
    	infoControls.add(getHeaderLabel("Info"));
    	infoControls.add(info);
        
    	
    	/***** GRAPH SIZE *****/
        final JLabel graphWindowSizeLabel = getNormalLabel("Hit Enter to apply!");
    	JTextField graphWindowSizeTextField = new JTextField("" + DEFAULT_GRAPH_WINDOW_SIZE);
    	graphWindowSizeTextField.addActionListener(
    			new ActionListener() {
    				public void actionPerformed(ActionEvent e) {
    					try {
    						graphWindowSize = Integer.parseInt(e.getActionCommand());
    						graphWindowSizeLabel.setText("Set to " + graphWindowSize + "ms.");
    					} catch(NumberFormatException ex) {
    						graphWindowSizeLabel.setText("Not a valid number!.");
    					}
    				}
    			}
    	);
    	//graphWindowSizeTextField.setPreferredSize(new Dimension(50,100));
    	//graphWindowSizeTextField.setColumns(10);
 	
    	Container graphWindowControls = getPanel(BoxLayout.Y_AXIS);
    	graphWindowControls.add(getHeaderLabel("Adjust Graph Window Size (ms)"));
    	graphWindowControls.add(graphWindowSizeTextField);
    	graphWindowControls.add(graphWindowSizeLabel);
    	
    	/***** STATE CONFIGURATIONS *****/	
    	final JRadioButton eightButton = new JRadioButton("Eight States");
    	eightButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				flagsProcessor = FlagsProcessorFactory.getEightStateProcessor();
				counter = new Counter(8);
			}
    	});
    	final JRadioButton fourButton = new JRadioButton("Four States (safe states)");
    	fourButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				flagsProcessor = FlagsProcessorFactory.getFourStateProcessor();
				counter = new Counter(4);
			}
    	});
    	final JRadioButton independentButton = new JRadioButton("3x Independent");
    	independentButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				flagsProcessor = FlagsProcessorFactory.getIndependentProcessor();
				counter = new Counter(8);
			}
    	});
 
    	JPanel stateModes = getPanel(BoxLayout.Y_AXIS);
       	stateModes.add(eightButton);
    	stateModes.add(fourButton);
    	stateModes.add(independentButton);

    	final ButtonGroup stateConfigButtonGroup = new ButtonGroup();
    	stateConfigButtonGroup.add(eightButton);
    	stateConfigButtonGroup.add(fourButton);
    	stateConfigButtonGroup.add(independentButton);
    
    	// Setup starters
    	eightButton.setSelected(true);	
    	
    	JPanel stateConfig = getPanel(BoxLayout.Y_AXIS);
    	stateConfig.add(getHeaderLabel("State Configuration"));
    	stateConfig.add(stateModes);
       	
    	/***** CONTROLS *****/
        final JButton startButton = new JButton("Start");
        startButton.setBackground(Color.GREEN);
        startButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
            	if(!running && storage.open(flagsProcessor.getDescription())) {
            		bufferedData = new LinkedList<EnergyData>();
            		updateTimer.start();
            		driver.start();
            		runningLabel.setText("YES");
            		running = true;
            		counter.reset();
            		{
            			eightButton.setEnabled(false);
            			fourButton.setEnabled(false);
            			independentButton.setEnabled(false);
            		}
            	}
            }
        });
        
        final JButton stopButton = new JButton("Stop");
        stopButton.setBackground(Color.RED);
        stopButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
            	if(running) {
            		updateTimer.stop();
            		driver.stop();
            		runningLabel.setText("NO");
            		running = false;
            		storage.add("#####");
            		storage.add("");
            		storage.add(counter.getHeaders());
            		storage.add(counter.getData());
            		storage.add(new String[] {"Total time: ",""+counter.getTotalTime(),"ms."});
            		storage.close();

            		{
           				eightButton.setEnabled(true);
               			fourButton.setEnabled(true);
               			independentButton.setEnabled(true);
            		}
            	}
            }
        });
        
        final JButton pauseButton = new JButton("Pause Graph");
        pauseButton.setBackground(Color.YELLOW);
        pauseButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
            	if(updateGraph) {
            		pauseButton.setText("   Resume    ");
            		updateGraph = false;
            	} else {
            		pauseButton.setText("Pause Graph");
            		updateGraph = true;
            	}
            }
        });
        
    	JPanel control = getPanel(BoxLayout.X_AXIS);
    	control.add(startButton);
    	control.add(stopButton);
    	control.add(pauseButton);
    	control.setAlignmentX(Component.LEFT_ALIGNMENT);
    	Container controlControls = getPanel(BoxLayout.Y_AXIS);
    	controlControls.add(getHeaderLabel("Controls"));
    	controlControls.add(control);
   
    	/***** CALIBRATION CONFIGURATIONS *****/

    	final JComboBox condensatorList = new JComboBox(Condensator.values());
    	final JLabel calibrationStatusLabel = getNormalLabel("WARNING: Zero not set!");
    	
    	calibrationStatusLabel.setForeground(Color.RED);
    	JButton calibrateButton = new JButton("Zero Reset");
    	calibrateButton.setBackground(Color.BLUE);
    	calibrateButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
            	
            	if(running) return;
            	
            	calibrationStatusLabel.setText("Setting Zero..");
            	calibrationStatusLabel.repaint();
            	
            	List<EnergyData> buffer = new LinkedList<EnergyData>();
            	
            	driver.start();
            	while(buffer.size()<10) {
            		try {
            			Thread.sleep(1000);
            		} catch (InterruptedException ex) {
            			log.error("Calibration interrupted.", ex);
            		}
            		buffer.addAll(driver.getdata());
            	}
				driver.stop();
				
				calibrator = new Calibrator((Condensator)condensatorList.getSelectedItem());
				calibrator.setEvenConstant(buffer.get(7), buffer.get(8));
				calibrator.setOddConstant(buffer.get(8), buffer.get(9));
				
            	calibrationStatusLabel.setForeground(Color.BLACK);
            	calibrationStatusLabel.setText("Calibrated with condensator " + condensatorList.getSelectedItem());
            }
        });

    	JPanel calibration = getPanel(BoxLayout.X_AXIS);
    	calibration.add(condensatorList);
    	calibration.add(calibrateButton);
    	calibration.setAlignmentX(Component.LEFT_ALIGNMENT);    	
    	JPanel calibrateConfig = getPanel(BoxLayout.Y_AXIS);
    	calibrateConfig.add(getHeaderLabel("Calibration Configuration"));
    	calibrateConfig.add(calibration);
    	calibrateConfig.add(calibrationStatusLabel);

    	/***** CHARGE COUNTER INFO *****/
    	
    	counterTable = new JTable(
    			new AbstractTableModel() {
    				public String getColumnName(int col) {
    			    	return counter.getHeaders()[col];
    			    }
    			    public int getRowCount() { return counter.getStates(); }
    			    public int getColumnCount() { return counter.getHeaders().length; }
    			    public Object getValueAt(int row, int col) {
    			    	return counter.getData()[row][col];
    			    }
    			    public boolean isCellEditable(int row, int col) { return false; }
    			    public void setValueAt(Object value, int row, int col) {
//    			        fireTableCellUpdated(row, col);
    			    }
    			}
    	);
    	
    	/*JPanel chargeInfo = getPanel(BoxLayout.Y_AXIS);
    	chargeInfo.add(getHeaderLabel("Charge Counter"));
    	chargeInfo.add(Box.createRigidArea(new Dimension(0,10)));
    	chargeInfo.add(chargeTable.getTableHeader());
    	chargeInfo.add(chargeTable);
    	chargeInfo.add(chargeTime);/
    	
    	
    	/***** ALL *****/
    	
    	JPanel controlPanel = getPanel(BoxLayout.Y_AXIS);
    	controlPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));         	
        controlPanel.add(infoControls);
        controlPanel.add(Box.createRigidArea(new Dimension(0,10)));
        controlPanel.add(controlControls);
        controlPanel.add(Box.createRigidArea(new Dimension(0,10)));      
        controlPanel.add(graphWindowControls);
        controlPanel.add(Box.createRigidArea(new Dimension(0,10)));
        controlPanel.add(stateConfig);
        controlPanel.add(Box.createRigidArea(new Dimension(0,10)));
        controlPanel.add(calibrateConfig);
        controlPanel.add(Box.createRigidArea(new Dimension(0,10)));
        /*controlPanel.add(chargeInfo);
        controlPanel.add(Box.createRigidArea(new Dimension(0,10)));*/
        
        controlPanel.add(Box.createVerticalStrut(100));
        //controlPanel.add(Box.createVerticalGlue());
        
        JPanel tablePanel = getPanel(BoxLayout.Y_AXIS);
        tablePanel.add(counterTable.getTableHeader());
    	tablePanel.add(counterTable);
        
    	graph = new Graph();
    	//graph.setSize(200, 200);

    	/*JPanel window = new JPanel(new GridLayout(1,2));
    	window.add(graph);
        window.add(controlPanel);*/
    	
    	JPanel window = new JPanel(new BorderLayout());
    	window.add(graph, BorderLayout.CENTER);
        window.add(controlPanel, BorderLayout.EAST);
        window.add(tablePanel, BorderLayout.SOUTH);
        
    	
    	JFrame frame = new JFrame();
    	frame.setTitle("Energy Bucket");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(window, BorderLayout.CENTER);
        frame.setSize(900, 600);
    	//frame.pack();
        frame.setVisible(true);
	}

	private JPanel getPanel(int axis) {
		JPanel panel = new JPanel();
    	panel.setLayout(new BoxLayout(panel, axis));
    	panel.setAlignmentX(Component.LEFT_ALIGNMENT);
    	return panel;
	}
	
	private JLabel getNormalLabel(String text) {
		JLabel label = new JLabel(text);
		label.setFont(label.getFont().deriveFont(Font.PLAIN));
		return label;
	}

	private JLabel getHeaderLabel(String text) {
		JLabel header = new JLabel(text);
		header.setForeground(new Color(255,0,0));
		header.setAlignmentX(Component.LEFT_ALIGNMENT);
		return header;
	}
	
}
